iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 23
0

This 完全取決於呼叫地點。

呼叫地點

就是函式被呼叫的地方。

function foo() {
    console.log( "foo" );
    bar();  // bar 的呼叫地點
}
function bar() {
    console.log( "bar" );
    baz();  // baz 的呼叫地點
}
function baz() {
    console.log( "baz" );
}

foo();  // foo 的呼叫地點

接著,什麼是呼叫堆疊?
Tony 用的是 chrom 的 dev tool 裡面的 performance。開啟這個腳本後,錄製。

在呼叫 baz 的過程中,有經過 foo 與 bar。也可以看出範疇存活的狀態。

不過就是規則

this 總共有四個規則,來決定指向的對象。

  1. 預設繫結
  2. 隱含的繫結
  3. 明確的繫結
  4. new 繫結

當然衝突的時候,也會有優先順序的問題。

1. 預設繫結

就是沒有規則時,指向的位置。

function foo() {
    var a = 3;
    console.log( this.a );
}
var a = 2;

foo(); // 2

他會指向全域的 2,而不是自身或範疇中的 3


但如果使用了嚴格模式。

function foo() {
    "use strict";
    var a = 3;
    console.log( this.a );
}
var a = 2;

foo(); // undefined

( 咦?還以為是 3,以為嚴格會包覆範疇)


其實是 this 找不到。

function foo() {
    "use strict";
    console.log( this, "haha" );
}
foo(); // undefined "haha"

但是嚴格模式,和他呼叫的地方沒有關係。

function foo(){
    console.log( this.a );
}

var a = 2;

(function(){
    "use strict"
    foo(); // 2
})();

2. 隱含的繫結

如果呼叫地點是在物件裡面。稱為

  • 情境物件 ( context object )
  • 擁有物件 ( owning object )
  • 包含物件 ( containing object )
function foo(){
    console.log( this.a );
}

var obj = {
    a: 2,
    foo: foo
};

obj.foo(); // 2

這是在物件 obj 裡面呼叫,所以指向物件本身。
可是其實 obj 不包含 foo 函式。
是屬性新增的參考,所以擁有物件和包含物件的稱呼並不恰當。


讓我們來更釐清一下。

function bar() {
    console.log( this.a );
}

var obj2 = {
    a: 42,
    foo: bar // 釐清用 可以直接用 foo
}

var obj1 = {
    a: 2,
    obj: obj2 // 釐清用 obj 可以直接用 obj2
}
obj1.obj.foo(); // 42

執行的位置在 obj2 裡面的 foo。


隱含的失去

就是 this 失去繫結,偷偷回到預設全域的狀態。
你只需要注意函式在哪裡執行就好,讓我們找出 () 吧。

function foo() {
    console.log( this.a );
}

var obj = {
    a: 2,
    foo: foo
}

var bar = obj.foo; // bar 其實就是 foo 了。( 參考 foo )

var a = "Global";  

bar();             // "Global"

這時候的 bar 與 obj 沒有關係。
( 有點閉包移除環境的感覺 )
所以執行 bar 就會直接使用預設繫結。


function foo() {
    console.log( this.a );
}

function doFoo(fn) {
    fn();
}

var obj = {
    a: 2,
    foo: foo
}

var a = "Global";

doFoo( obj.foo );

聰明如你,一定猜的到是 "Global"。但為什麼呢?
只要看呼叫函式的地點就可以了。
就在 doFoo 裡面的 fn()。這時候的 this 就會指向預設的地方。


最後還有 setTimeout 的使用,也會讓 callback 延後執行。

(總總以上的三項,都有相當類似 closure 讓環境消失的做法)

3. 明確的繫結

這種似曾相似感,在先前的強制轉型有看過。

明確的繫結,就是由你指定 this 繫結的對象。

藉由函式的 prototype 裡面的 call(..) 和 apply(..) 來達成。

function foo() {
    console.log( this.a );
}

var obj = {
    a: 2
};
foo.call( obj ); // 2

硬繫結 ( hard binding )

硬繫結,藉由明確的繫結,建立無可替代的繫結。

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2
};

var bar = function() {
    foo.call( obj );
};

bar();                 // 2
setTimeout( bar, 100); // 2
bar.call( window );    // 2

不管如何調用 bar,他都會永遠以 obj 為最後的 this。因為裡面的 call 已經指定好了。


書上還有提供許多厲害的硬繫結。這邊先略過。
但每個都超有趣的!

4. new 繫結

new 是運算式。在使用時,會做三件事

  • 建立一個空物件。這物件會帶有 [[ prototype ]]
  • 當執行環境執行時,this 變數會指向空物件。
  • 若函式不回傳任何東西的話,回傳新物件,給定的屬性和方法。

就是函式前面加個 new,所以只有函式不回傳東西,JS 引擎才會幫你生出一個物件。

function foo(a) {
    this.a = a;
}

var bar = new foo( 2 );
console.log( bar.a );   // 2

因為 foo() 沒有回傳值,就建了一個 bar 的物件,他的屬性有 a: 2。
當然 this 就會指向 bar 了。

明天

來排優先順序吧! 到底 this 歸誰呢?
未看先猜,預設繫結是撿剩的。

明天見!

參考資料

  1. 你所不知道的JS
  2. 克服 JavaScript 奇怪的地方

上一篇
Day22 - 這些不是 this
下一篇
Day24 - 哪個 this 比較大?
系列文
你為什麼不問問神奇 JavaScript 呢?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言